﻿using System;
using System.Diagnostics;

namespace SfsTools.Core
{
    /// <summary>
    /// Represents a region of blocks.
    /// </summary>
    internal struct BlockRegion : ICloneable
    {
        #region Properties

        /// <summary>
        /// Contains the number of the first block in the region.
        /// </summary>
        private Int64 FStart;
        /// <summary>
        /// Gets or sets the number of the first block in the region.
        /// </summary>
        /// <exception cref="ArgumentOutOfRangeException">The argument <paramref name="value"/> is out of valid range.</exception>
        public Int64 Start
        {
            [DebuggerStepThrough]
            get
            {
                return FStart;
            }
            [DebuggerStepThrough]
            set
            {
                if (value < 0)
                    throw new ArgumentOutOfRangeException("value");
                FStart = value;
            }
        }

        /// <summary>
        /// Contains the count of blocks in the region.
        /// </summary>
        private Int64 FLength;
        /// <summary>
        /// Gets or sets the count of blocks in the region.
        /// </summary>
        /// <exception cref="ArgumentOutOfRangeException">The argument <paramref name="value"/> is out of valid range.</exception>
        public Int64 Length
        {
            [DebuggerStepThrough]
            get
            {
                return FLength;
            }
            [DebuggerStepThrough]
            set
            {
                if (value < 0)
                    throw new ArgumentOutOfRangeException("value");
                FLength = value;
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Determines whether this block region overlaps with <paramref name="region"/>
        /// </summary>
        /// <param name="region">The other block region.</param>
        /// <returns></returns>
        [DebuggerStepThrough]
        public bool IsOverlapping(BlockRegion region)
        {
            // ReSharper disable RedundantThisQualifier
            long startA = this.FStart, startB = region.FStart;
            long endA = this.FStart + this.FLength, endB = region.FStart + region.FLength;
            // ReSharper restore RedundantThisQualifier
            // If either start or end of other region falls inside this region, it's an overlap.
            if ((startB >= startA && startB < endA) ||
                (endB > startA && endB <= endA))
                return true;
            // If either start or end of this region falls inside other region, it's an overlap.
            if ((startA >= startB && startA < endB) ||
                (endA > startB && endA <= endB))
                return true;
            // Sorry.
            return false;
        }

        /// <summary>
        /// Determines whether this block region fully contains <paramref name="region"/>.
        /// </summary>
        /// <param name="region">The other block region.</param>
        /// <returns></returns>
        [DebuggerStepThrough]
        public bool Contains(BlockRegion region)
        {
            // ReSharper disable RedundantThisQualifier
            long startA = this.FStart, startB = region.FStart;
            long endA = this.FStart + this.FLength, endB = region.FStart + region.FLength;
            // ReSharper restore RedundantThisQualifier
            return ((startB >= startA) && (endB <= endA));
        }
        
        /// <summary>
        /// Returns a <see cref="String"/> that represents the current <see cref="BlockRegion"/>.
        /// </summary>
        /// <returns>A <see cref="String"/> that represents the current <see cref="BlockRegion"/>.</returns>
        [DebuggerStepThrough]
        public override string ToString()
        {
            //return new System.Text.StringBuilder().
            //    Append('{').Append(FStart).Append('→').Append(FLength).Append('}').
            //    ToString();
            return new System.Text.StringBuilder().
                Append('{').Append(FStart).Append('/').Append(FLength).Append('}').
                ToString();
        }

        /// <overloads>Determines whether this instance equals <paramref name="value"/>.</overloads>
        /// <summary>
        /// Determines whether this instance equals <paramref name="value"/>.
        /// </summary>
        /// <param name="value">The <see cref="Object"/> to compare.</param>
        /// <returns><see langword="true"/> if this instance equals <paramref name="value"/>, <see langword="false"/> otherwise.</returns>
        [DebuggerStepThrough]
        public override bool Equals(Object value)
        {
            if (value is BlockRegion)
                return Equals((BlockRegion)value);
            else
                return base.Equals(value);
        }

        /// <summary>
        /// Determines whether this instance equals <paramref name="value"/>.
        /// </summary>
        /// <param name="value">The <see cref="BlockRegion"/> to compare.</param>
        /// <returns><see langword="true"/> if this instance equals <paramref name="value"/>, <see langword="false"/> otherwise.</returns>
        [DebuggerStepThrough]
        public bool Equals(BlockRegion value)
        {
            // ReSharper disable RedundantThisQualifier
            return this.FStart == value.FStart && this.FLength == value.FLength;
            // ReSharper restore RedundantThisQualifier
        }
        
        /// <summary>
        /// Serves as a hash function for a particular type. <see cref="BlockRegion.GetHashCode"/> is suitable for use in hashing algorithms and data structures like a hash table.
        /// </summary>
        /// <returns>A hash code for the current <see cref="BlockRegion"/>.</returns>
        [DebuggerStepThrough]
        public override int GetHashCode()
        {
            return FStart.GetHashCode() ^ FLength.GetHashCode();
        }

        #endregion

        #region Constructors
        /// <overloads>Initializes a new instance of <see cref="BlockRegion"/>.</overloads>
        /// <summary>
        /// Initializes a new instance of <see cref="BlockRegion"/>.
        /// </summary>
        /// <param name="start">The first block in the region.</param>
        /// <param name="length">The count of blocks in the region.</param>
        [DebuggerStepThrough]
        public BlockRegion(Int64 start, Int64 length)
        {
            if (start < 0)
                throw new ArgumentOutOfRangeException("start");
            if (length < 0)
                throw new ArgumentOutOfRangeException("length");
            FStart = start;
            FLength = length;
        }
        /// <summary>
        /// Initializes a new instance of <see cref="BlockRegion"/> from an existing instace.
        /// </summary>
        /// <param name="value">The instance to copy.</param>
        [DebuggerStepThrough]
        public BlockRegion(BlockRegion value)
        {
            // ReSharper disable RedundantThisQualifier
            this.FStart = value.FStart;
            this.FLength = value.FLength;
            // ReSharper restore RedundantThisQualifier
        }
        #endregion
                
        #region ICloneable Members
        
        /// <summary>
        /// Creates a new <see cref="BlockRegion"/> that is the copy of the current instance.
        /// </summary>
        /// <returns>A new <see cref="BlockRegion"/> that is the copy of the current instance.</returns>
        [DebuggerStepThrough]
        public BlockRegion Clone()
        {
            return new BlockRegion(this);
        }
        
        /// <summary>
        /// Creates a new <see cref="BlockRegion"/> that is the copy of the current instance.
        /// </summary>
        /// <returns>A new <see cref="BlockRegion"/> that is the copy of the current instance.</returns>
        [DebuggerStepThrough]
        object ICloneable.Clone()
        {
            return new BlockRegion(this);
        }
        
        #endregion
    }
}
